package com.shen.library_nettysocket.netty

import android.media.MediaDrm
import com.shen.library_nettysocket.ExecutorServiceFactory
import com.shen.library_nettysocket.IMSConfig
import com.shen.library_nettysocket.MsgDispatcher
import com.shen.library_nettysocket.handler.HeartbeatHandler
import com.shen.library_nettysocket.handler.TCPChannelInitializerHandler
import com.shen.library_nettysocket.handler.TCPReadHandler
import com.shen.library_nettysocket.interf.IMSClientInterface
import com.shen.library_nettysocket.listener.IMSConnectStatusCallback
import com.shen.library_nettysocket.listener.OnEventListener
import com.shen.library_nettysocket.protobuf.MessageProtobuf
import com.shen.library_nettysocket.timer.MsgTimeoutTimerManager
import io.netty.channel.EventLoopGroup
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.nio.NioSocketChannel
import io.netty.channel.ChannelOption
import io.netty.handler.timeout.IdleStateHandler
import io.netty.bootstrap.Bootstrap
import io.netty.channel.Channel
import io.netty.util.internal.StringUtil
import java.lang.Exception
import java.util.*
import java.util.concurrent.TimeUnit

/**
 * 基于netty实现的tcp ims
 */
class NettyTcpClient private constructor() : IMSClientInterface {


    companion object {
        @Volatile
        var instance: NettyTcpClient? = null
            get() {
                if (null == field) {
                    synchronized(NettyTcpClient::class.java) {
                        if (null == field) {
                            field = NettyTcpClient()
                        }
                    }
                }
                return field
            }
            private set
    }


    override val isNetworkAvailable: Boolean
        get() = mOnEventListener?.isNetworkAvailable ?: false

    override var isClosed = false
        private set

    override var reconnectInterval: Int = IMSConfig.DEFAULT_RECONNECT_BASE_DELAY_TIME
        get() = if (mOnEventListener != null && mOnEventListener!!.reconnectInterval > 0) {
            mOnEventListener!!.reconnectInterval.also { field = it }
        } else field
        private set

    override var connectTimeout: Int = IMSConfig.DEFAULT_CONNECT_TIMEOUT
        get() = if (mOnEventListener != null && mOnEventListener!!.connectTimeout > 0) {
            mOnEventListener!!.connectTimeout.also { field = it }
        } else field
        private set

    /** 获取心跳间隔时间 */
    var heartbeatInterval: Int = IMSConfig.DEFAULT_HEARTBEAT_INTERVAL_FOREGROUND
        private set

    override var foregroundHeartbeatInterval: Int = IMSConfig.DEFAULT_HEARTBEAT_INTERVAL_FOREGROUND
        get() = if (mOnEventListener != null && mOnEventListener!!.foregroundHeartbeatInterval > 0) {
            mOnEventListener!!.foregroundHeartbeatInterval.also { field = it }
        } else field
        private set

    override var backgroundHeartbeatInterval: Int = IMSConfig.DEFAULT_HEARTBEAT_INTERVAL_BACKGROUND
        get() = if (mOnEventListener != null && mOnEventListener!!.backgroundHeartbeatInterval > 0) {
            mOnEventListener!!.backgroundHeartbeatInterval.also { field = it }
        } else field
        private set

    /** app前/后台状态 */
    private var appStatus: Int = IMSConfig.APP_STATUS_FOREGROUND

    override var resendCount: Int = IMSConfig.DEFAULT_RESEND_COUNT
        get() = if (mOnEventListener != null && mOnEventListener!!.resendCount != 0) {
            mOnEventListener!!.resendCount.also { field = it }
        } else field
        private set
    override var resendInterval: Int = IMSConfig.DEFAULT_RESEND_INTERVAL
        get() = if (mOnEventListener != null && mOnEventListener!!.reconnectInterval != 0) {
            mOnEventListener!!.resendInterval.also { field = it }
        } else field
        private set

    override var msgDispatcher:MsgDispatcher? = null // 消息转发器
        private set
    override var msgTimeoutTimerManager: MsgTimeoutTimerManager? = null
        private set

    override val handshakeMsg: MessageProtobuf.Msg?
        get() = mOnEventListener?.handshakeMsg
    override val heartbeatMsg:  MessageProtobuf.Msg?
        get() = mOnEventListener?.heartbeatMsg

    override val serverSentReportMsgType: Int
        get() = mOnEventListener?.serverSentReportMsgType ?: 0
    override val clientReceivedReportMsgType: Int
        get() =  mOnEventListener?.clientReceivedReportMsgType ?: 0

    private var bootstrap: Bootstrap? = null
    private var channel: Channel? = null
    /** ims服务器地址组 */
    private var serverUrlList: Vector<String?>? = null
    /** 与应用层交互的listener */
    private var mOnEventListener: OnEventListener? = null
    /** ims「连接状态」回调监听器 */
    private var mIMSConnectStatusCallback: IMSConnectStatusCallback? = null

    /** 线程池工厂 */
    var loopGroup: ExecutorServiceFactory? = null
        private set
    /** 是否正在进行重连 */
    private var isReconnecting = false
    /** ims连接状态，初始化为「连接失败」 */
    private var connectStatus: Int = IMSConfig.CONNECT_STATE_FAILURE
    /** 当前连接·host */
    private var currentHost: String? = null
    /** 当前连接·port */
    private var currentPort = -1 // 当前连接port


    /**
     * 初始化
     *
     * @param serverUrlList 服务器地址列表
     * @param listener      与应用层交互的listener
     * @param callback      ims连接状态回调
     */
    override fun init(serverUrlList: Vector<String?>?, listener: OnEventListener?, callback: IMSConnectStatusCallback?) {
        close()
        isClosed = false
        this.serverUrlList = serverUrlList
        mOnEventListener = listener
        mIMSConnectStatusCallback = callback

        msgDispatcher = MsgDispatcher().apply {
            setOnEventListener(listener)
        }
        loopGroup = ExecutorServiceFactory().apply {
            initBossLoopGroup()                             // 初始化重连线程组
        }

        msgTimeoutTimerManager = MsgTimeoutTimerManager(this)
        resetConnect(true)                             // 进行第一次连接
    }

    override fun resetConnect(){
        resetConnect(false)
    }

    override fun resetConnect(isFirst: Boolean) {
        if (!isFirst) {
            try {
                Thread.sleep(IMSConfig.DEFAULT_RECONNECT_INTERVAL)
            } catch (e: InterruptedException) {
                e.printStackTrace()
            }
        }

        // 只有第一个调用者才能赋值并调用重连
        if (!isClosed && !isReconnecting) {
            synchronized(this) {
                if (!isClosed && !isReconnecting) {
                    isReconnecting = true                                         // 标识「正在进行·重连」
                    onConnectStatusCallback(IMSConfig.CONNECT_STATE_CONNECTING)   // 回调ims连接状态
                    closeChannel()                                                // 先关闭channel
                    // 执行重连任务
                    loopGroup?.execBossTask(ResetConnectRunnable(isFirst))
                }
            }
        }
    }


    override fun close() {
        if (isClosed) {
            return
        }

        isClosed = true

        // 关闭「channel」
        try {
            closeChannel()
        } catch (ex: Exception) {
            ex.printStackTrace()
        }

        // 关闭「bootstrap」
        try {
            bootstrap?.group()?.shutdownGracefully()
        } catch (ex: Exception) {
            ex.printStackTrace()
        }

        try {
            loopGroup?.destroy()                        // 释放线程池
        } catch (ex: Exception) {
            ex.printStackTrace()
        } finally {
            try {
                serverUrlList?.clear()
            } catch (ex: Exception) {
                ex.printStackTrace()
            }

            isReconnecting = false
            channel = null
            bootstrap = null
        }
    }

    override fun sendMsg(msg: MessageProtobuf.Msg?) {
        sendMsg(msg, true)
    }

    /**
     * 发送消息
     * 重载
     *
     * @param msg
     * @param isJoinTimeoutManager 是否加入发送超时管理器
     */
    override fun sendMsg(msg: MessageProtobuf.Msg?, isJoinTimeoutManager: Boolean) {
        if (msg?.head == null) {
            println("发送消息失败，消息为空\tmessage=$msg")
            return
        }

        if (!msg.head.msgId.isNullOrEmpty()) {
            if (isJoinTimeoutManager) {
                msgTimeoutTimerManager?.add(msg)
            }
        }
        if (channel == null) println("发送消息失败，channel为空\tmessage=$msg")

        try {
            channel?.writeAndFlush(msg)
        } catch (ex: Exception) {
            println("发送消息失败，reason:" + ex.message + "\tmessage=" + msg)
        }
    }



    /**
     * 设置app前后台状态
     *
     * @param appStatus
     */
    override fun setAppStatus(appStatus: Int) {
        this.appStatus = appStatus

        when(appStatus){
            IMSConfig.APP_STATUS_FOREGROUND -> heartbeatInterval = foregroundHeartbeatInterval
            IMSConfig.APP_STATUS_BACKGROUND -> heartbeatInterval = backgroundHeartbeatInterval
        }
        addHeartbeatHandler()
    }

    /**
     * 初始化bootstrap
     */
    private fun initBootstrap() {
        val loopGroup: EventLoopGroup = NioEventLoopGroup(4)
        bootstrap = Bootstrap().apply {
            group(loopGroup).channel(NioSocketChannel::class.java)
            option(ChannelOption.SO_KEEPALIVE, true)    // 设置该选项以后，如果在「两小时内·没有数据的通信」时，TCP会自动发送一个活动探测数据报文
            option(ChannelOption.TCP_NODELAY, true)                         // 设置「禁用·nagle算法」
            option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout)          // 设置「连接超时·时长」
            handler(TCPChannelInitializerHandler(this@NettyTcpClient))    // 设置「初始化Channel」
        }
    }

    /**
     * 回调ims连接状态
     *
     * @param connectStatus
     */
    private fun onConnectStatusCallback(connectStatus: Int) {
        this.connectStatus = connectStatus

        when (connectStatus) {
            IMSConfig.CONNECT_STATE_CONNECTING -> {
                println("ims连接中...")
                mIMSConnectStatusCallback?.onConnecting()
            }

            IMSConfig.CONNECT_STATE_SUCCESSFUL -> {
                println(String.format("ims连接成功，host『%s』, port『%s』", currentHost, currentPort))
                mIMSConnectStatusCallback?.onConnected()

                // 连接成功，发送握手消息
                val handshakeMsg: MessageProtobuf.Msg? = handshakeMsg
                if (handshakeMsg != null) {
                    println("发送握手消息，message=$handshakeMsg")
                    sendMsg(handshakeMsg, false)
                } else {
                    System.err.println("请应用层构建握手消息！")
                }
            }

            IMSConfig.CONNECT_STATE_FAILURE -> {
                println("ims连接失败")
                mIMSConnectStatusCallback?.onConnectFailed()
            }

            else -> {
                println("ims连接失败")
                mIMSConnectStatusCallback?.onConnectFailed()
            }
        }
    }

    /**
     * 添加「心跳消息」管理handler
     */
    fun addHeartbeatHandler() {
        try {
            channel?.run {
                if(!isActive || pipeline() == null) return

                // 「之前存在」的「读写超时handler」，先移除掉，再重新添加
                if (pipeline()[IdleStateHandler::class.java.simpleName] != null)
                    pipeline().remove(IdleStateHandler::class.java.simpleName)

                // 3次心跳没响应，代表连接已断开
                pipeline().addFirst(IdleStateHandler::class.java.simpleName, IdleStateHandler(
                        (heartbeatInterval * 3).toLong(),       // 读取时长是：3次心跳时长
                        heartbeatInterval.toLong(),
                        0, TimeUnit.MILLISECONDS)
                )

                // 重新添加HeartbeatHandler
                if (pipeline()[HeartbeatHandler::class.java.simpleName] != null)
                    pipeline().remove(HeartbeatHandler::class.java.simpleName)

                if (pipeline()[TCPReadHandler::class.java.simpleName] != null) {
                    pipeline().addBefore(
                        TCPReadHandler::class.java.simpleName,
                        HeartbeatHandler::class.java.simpleName,
                        HeartbeatHandler(this@NettyTcpClient)
                    )
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
            System.err.println("添加心跳消息管理handler失败，reason：" + e.message)
        }
    }

    /**
     * 移除指定handler
     *
     * @param handlerName
     */
    private fun removeHandler(handlerName: String) {
        try {
            channel?.run {
                if (pipeline()[handlerName] != null) {
                    pipeline().remove(handlerName)
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
            System.err.println("移除handler失败，handlerName=$handlerName")
        }
    }

    /**
     * 关闭channel
     */
    private fun closeChannel() {
        try {
            if (channel != null) {
                try {
                    removeHandler(HeartbeatHandler::class.java.simpleName)
                    removeHandler(TCPReadHandler::class.java.simpleName)
                    removeHandler(IdleStateHandler::class.java.simpleName)
                } finally {
                    try {
                        channel!!.close()
                    } catch (ex: Exception) {
                    }

                    try {
                        channel!!.eventLoop().shutdownGracefully() // 释放「线程池资源」
                    } catch (ex: Exception) {
                    }

                    channel = null
                }
            }
        } catch (ex: Exception) {
            ex.printStackTrace()
            println("关闭channel出错，reason:" + ex.message)
        }
    }




    /**
     * 重连任务
     */
    private inner class ResetConnectRunnable(private val isFirst: Boolean) : Runnable {
        override fun run() {
            // 「非首次」进行重连，执行到这里即代表已经连接失败，回调连接状态到应用层
            if (!isFirst) {
                onConnectStatusCallback(IMSConfig.CONNECT_STATE_FAILURE)
            }

            try {
                loopGroup?.destroyWorkLoopGroup()           // 重连时，释放工作线程组，也就是停止心跳

                while (!isClosed) {
                    if (!isNetworkAvailable) {              // 网络不好 -- 2秒重连一次
                        try {
                            Thread.sleep(2000)
                        } catch (e: InterruptedException) {
                            e.printStackTrace()
                        }
                        continue
                    }

                    // 网络可用才进行连接
                    when(reConnect()){
                        IMSConfig.CONNECT_STATE_SUCCESSFUL ->{      // 连接成功，跳出循环
                            onConnectStatusCallback(IMSConfig.CONNECT_STATE_SUCCESSFUL)
                            break
                        }
                        IMSConfig.CONNECT_STATE_FAILURE -> {
                            onConnectStatusCallback(IMSConfig.CONNECT_STATE_FAILURE)
                            try {
                                Thread.sleep(IMSConfig.DEFAULT_RECONNECT_INTERVAL)
                            } catch (e: InterruptedException) {
                                e.printStackTrace()
                            }
                        }
                    }
                }
            } finally {
                isReconnecting = false                  // 标识「重连任务·停止」
            }
        }

        /**
         * 重连，首次连接也认为是第一次重连
         * @return 连接状态
         */
        private fun reConnect(): Int {
            // 「未关闭」才去连接
            if (!isClosed) {
                try {
                    bootstrap?.group()?.shutdownGracefully()    // 先释放EventLoop线程组
                } finally {
                    bootstrap = null
                }

                initBootstrap()                                 // 初始化bootstrap
                return connectServer()
            }
            return IMSConfig.CONNECT_STATE_FAILURE
        }

        /**
         * 连接「服务器」
         * @return
         */
        private fun connectServer(): Int {
            // 如果服务器地址无效，直接回调连接状态，不再进行连接
            // 有效的服务器地址示例：127.0.0.1 8860
            if (serverUrlList == null || serverUrlList!!.size == 0) {
                return IMSConfig.CONNECT_STATE_FAILURE
            }

            var i = 0
            while (!isClosed && i < serverUrlList!!.size) {
                val serverUrl = serverUrlList!![i]
                if (serverUrl.isNullOrEmpty()) {        // 如果「服务器地址无效」，直接回调连接状态，不再进行连接
                    return IMSConfig.CONNECT_STATE_FAILURE
                }
                val address = serverUrl.split(" ").toTypedArray()   // 分离「地址」和「端口号」
                for (j in 1..IMSConfig.DEFAULT_RECONNECT_COUNT) {
                    if (isClosed || !isNetworkAvailable) {                     // 如果ims已关闭，或网络不可用，直接回调连接状态，不再进行连接
                        return IMSConfig.CONNECT_STATE_FAILURE
                    }

                    // 回调连接状态
                    if (connectStatus != IMSConfig.CONNECT_STATE_CONNECTING) {
                        onConnectStatusCallback(IMSConfig.CONNECT_STATE_CONNECTING)
                    }

                    println(String.format("正在进行『%s』的第『%d』次连接，当前重连延时时长为『%dms』", serverUrl,
                            j, j * reconnectInterval))

                    try {
                        currentHost = address[0]            // 获取host
                        currentPort = address[1].toInt()    // 获取port
                        toServer()                          // 连接服务器


                        if (channel != null) {              // channel不为空，即认为连接已成功
                            return IMSConfig.CONNECT_STATE_SUCCESSFUL
                        } else {                            // 连接失败，则线程休眠「n * 重连间隔」时长
                            Thread.sleep((j * reconnectInterval).toLong())
                        }
                    } catch (e: InterruptedException) {
                        close()
                        break // 线程被中断，则强制关闭
                    }
                }
                i++
            }

            return IMSConfig.CONNECT_STATE_FAILURE          // 执行到这里，代表连接失败
        }
    }

    /**
     * 真正连接服务器的地方 -- 会设置[channel] 如果为[null]链接服务器失败
     */
    private fun toServer() {
        channel = try {
            bootstrap!!.connect(currentHost, currentPort).sync().channel()
        } catch (e: Exception) {
            try {
                Thread.sleep(500)
            } catch (e1: InterruptedException) {
                e1.printStackTrace()
            }
            System.err.println(String.format("连接Server(ip[%s], port[%s])失败", currentHost, currentPort))
            null
        }
    }

}